﻿using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using T.FAS.Runtime.Base.Common;
using T.FAS.Runtime.Base.Dao;
using T.FAS.Runtime.Base.Entity;
using T.ServiceComponent.Kernel.Service;
using T.ServiceComponent.Log.Client;
using T.ServiceComponent.Log.SPI;

namespace T.FAS.Runtime.Base.Service
{
    public class FasLogService
    {
        private static readonly object syncObj = new object();

        private ConcurrentBag<DataChangeLog> logStore = null;

        private static FasLogService instance;

        private bool isExist = false;

        private AutoResetEvent autoResetLog;

        private int capacity = 100000;

        private readonly ChangeLogDao changelogDao;
        private readonly ChangeLogDetailDao changelogDetailDao;
        private bool isFull = false;

        private readonly string logType = ConfigUtil.Instance.LogType;
        private FasLogService()
        {
            logStore = new ConcurrentBag<DataChangeLog>();
            autoResetLog = new AutoResetEvent(false);
            changelogDao = new ChangeLogDao();
            changelogDetailDao = new ChangeLogDetailDao();
            StartLogProcess();
        }

        public void Stop()
        {
            isExist = true;
            autoResetLog.Set();
        }

        public void Restart()
        {
            isExist = false;
            autoResetLog = new AutoResetEvent(false);
            StartLogProcess();
        }


        public static FasLogService GetInstance()
        {
            if (instance == null)
            {
                lock (syncObj)
                {
                    if (instance == null)
                    {
                        instance = new FasLogService();
                    }
                }
            }

            return instance;
        }

        public void AddLog(DataChangeLog log)
        {
            if (log == null) return;

            if (isFull)
            {
                return;
            }

            if (logType == "SplitTable")
                FasSplitLogService.GetInstance().AddLog(log);
            logStore.Add(log);
        }

        public void AddLogs(IEnumerable<DataChangeLog> logs)
        {
            if (logs == null || logs.Count() == 0)
            {
                return;
            }
            if (isFull)
            {
                return;
            }

            foreach (var i in logs)
            {
                if (i != null)
                {
                    i.ID = Guid.NewGuid().ToString();
                    i.CreateTime = i.CreateTime.Year == 1 ? DateTime.Now : i.CreateTime;
                    i.Ext19 = string.IsNullOrEmpty(i.Ext19) ? "T" : i.Ext19;
                    logStore.Add(i);
                }
            }
            if (logType == "SplitTable")
                FasSplitLogService.GetInstance().AddLogs(logs);
        }

        private void StartLogProcess()
        {
            Task.Factory.StartNew(() =>
            {
                //重新初始化一下RequestID，防止日志记录在正常链路中。
                //同时这些日志不需要记录全链路了，因为没有意义，所以初始化RequestID为空
                RpcContext.Current.RequestID = "";

                while (!isExist)
                {
                    var start = DateTime.Now;
                    var logs = new List<DataChangeLog>();
                    DataChangeLog log = null;
                    var index = 0;

                    if (logStore?.Count >= capacity)
                    {
                        isFull = true;
                    }
                    else
                    {
                        isFull = false;
                    }

                    if (logStore?.IsEmpty == false)
                    {
                        while ((DateTime.Now - start).TotalMilliseconds < 1000 && index < 100)
                        {
                            var result = logStore.TryTake(out log);
                            if (result && log != null)
                            {
                                index++;
                                logs.Add(log);
                            }
                        }
                    }
                    try
                    {
                        if (logs != null && logs.Count > 0)
                        {
                            if (logType == "BIGDATA")
                            {
                                AddLogToBigData(logs);
                            }
                            else
                            {
                                AddLogToDB(logs);
                            }
                            logs.Clear();
                            index = 0;
                        }
                        else
                        {
                            autoResetLog.WaitOne(1000);
                        }
                    }
                    catch (Exception e)
                    {
                        try
                        {
                            if (logs != null && logs.Count > 0)
                            {
                                if (logType == "BIGDATA")
                                {
                                    AddLogToDB(logs);
                                }
                                else
                                {
                                    AddLogToBigData(logs);
                                }
                                logs.Clear();
                                index = 0;
                            }
                            else
                            {
                                autoResetLog.WaitOne(1000);
                            }
                        }
                        catch (Exception ex)
                        {
                        }
                    }
                }
            });
        }
        private void AddLogToBigData(List<DataChangeLog> logs)
        {
            using (var client = new LogLocalClient())
            {
                client.SendLogs(logs.ToArray());
            }
        }
        private void AddLogToDB(List<DataChangeLog> logs)
        {
            List<ChangeLog> changeLogs = new List<ChangeLog>();
            List<ChangeLogDetail> changeLogDetails = new List<ChangeLogDetail>();
            foreach (var item in logs)
            {
                ChangeLog changeLog = new ChangeLog();
                changeLog.ID = Guid.NewGuid().ToString();
                changeLog.CreateTime = item.CreateTime;
                changeLog.BillID = item.BillID;
                changeLog.OldStateCode = item.Ext11;
                changeLog.OldStateName = item.Ext12;
                changeLog.NewStateCode = item.Ext13;
                changeLog.NewStateName = item.Ext14;
                changeLog.CDOID = item.Ext15;
                changeLog.TenantID = item.Ext16;
                changeLog.CDOCode = item.Ext17;
                changeLog.CDOName = item.Ext18;
                changeLog.UserID = item.Ext19;
                changeLog.UserName = item.Ext19;
                changeLog.Action = item.Action;
                for (int i = 0; i < item.Datas.Count; i++)
                {
                    ChangeLogDetail changeLogDetail = new ChangeLogDetail();
                    changeLogDetail.ID = Guid.NewGuid().ToString();
                    changeLogDetail.SortOrder = i;
                    changeLogDetail.ChangeLogID = changeLog.ID;
                    changeLogDetail.BillID = changeLog.BillID;
                    changeLogDetail.DataColumn = item.Datas[i].DataColumn;
                    changeLogDetail.OldData = item.Datas[i].OldData;
                    changeLogDetail.NewData = item.Datas[i].NewData;
                    changeLogDetails.Add(changeLogDetail);
                }
                changeLogs.Add(changeLog);
            }
            using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
            {
                changelogDao.Add(changeLogs);
                changelogDetailDao.Add(changeLogDetails);
                scope.Complete();
            }
        }
    }
}
